import hmac
import json
import logging
import time
import urllib.parse

import requests
import requests.auth

from coin.exchange.base.kr_rest.auth_base import AuthKey
from coin.exchange.base.kr_rest.rest_client_base import RestUpdate
from coin.exchange.util.rate_limit import RateLimiter

BYBIT_API_URL = "https://api.bybit.com"


def _get_server_timestamp():
  # path = BYBIT_API_URL + '/v2/public/time'
  # r = requests.get(path);int(float(r.json()['time_now']) * 1000)
  # reduce query by half to avoid api ban
  import time
  return int(time.time() * 1000)


class BybitAuth(requests.auth.AuthBase):
  def __init__(self, key_file):
    self._key = AuthKey.from_file(key_file)

  def _get_signed_params(self, full_params):
    sorted_params = sorted(full_params.items(), key=lambda d: d[0])
    encode_params = urllib.parse.urlencode(sorted_params).encode()
    sign = str(hmac.new(self._key._secret_key_bytes, encode_params, digestmod="sha256").hexdigest())
    return sign

  def __call__(self, req: requests.PreparedRequest):
    # assert req.method == 'GET', req.method
    parsed_result = urllib.parse.urlparse(req.url)
    # noinspection PyArgumentList
    url = urllib.parse.ParseResult(parsed_result.scheme,
                                   parsed_result.netloc,
                                   parsed_result.path,
                                   '',
                                   '',
                                   '').geturl()
    timestamp = _get_server_timestamp()
    full_params = {'api_key': self._key.api_key, 'timestamp': timestamp}
    params = urllib.parse.parse_qsl(parsed_result.query)
    full_params.update(params)
    sign = self._get_signed_params(full_params)
    full_params['sign'] = sign
    req.prepare_url(url, full_params)
    return req


class BybitSyncQueryer(object):
  def __init__(
      self,
      key_file,
      *,
      api_host=None,
      timeout=10,
  ):
    self._timeout = timeout
    self._auth = BybitAuth(key_file)
    self._api_host = api_host or BYBIT_API_URL
    self._logger = logging.getLogger(__name__)
    self.rate_limit = RateLimiter(20, 1.0)

  def auth_request(self, method, path, params):
    # assert method == 'GET', method
    url = urllib.parse.urljoin(self._api_host, path)
    response = None
    try:
      response = requests.request(method,
                                  url,
                                  params=params,
                                  auth=self._auth,
                                  timeout=self._timeout)
      content = response.json()
      return content
    except requests.exceptions.RequestException:
      self._logger.exception('RequestException: %s')
      raise
    except json.JSONDecodeError:
      self._logger.exception('Json decode: %s', response.content)
      raise
    except Exception as e:
      self._logger.exception('Unknown exception, %s', e)
      raise

  def _query_nocheck(self, method, path, params):
    req_timestamp = time.time()
    content = self.auth_request(method, path, params)
    res_timestamp = time.time()
    status = requests.codes.ok
    update = RestUpdate(req_timestamp, res_timestamp, content, status)
    return update

  def query(self, method, path, params=None, disable_rate_limit=False):
    if not disable_rate_limit:
      while not self.rate_limit.check():
        time.sleep(0.1)
    else:
      self.rate_limit.check()
    return self._query_nocheck(method, path, params)


class BybitNativePrivateClient(object):
  def __init__(
      self,
      *,
      key_file,
      api_host=None,
      timeout=20,
      use_async=False,
  ):
    api_host = api_host or BYBIT_API_URL
    if use_async:
      raise NotImplementedError()
    else:
      self._auth_queryer = BybitSyncQueryer(
          key_file,
          api_host=api_host,
          timeout=timeout,
      )

  def _query(self, method, path, params=None):
    return self._auth_queryer.query(method, path, params=params)

  def query_account_balance(self, coin, recv_window=20000):
    method = 'GET'
    path = 'v2/private/wallet/balance'
    params = {'coin': coin, 'recv_window': recv_window}
    update = self._query(method, path, params)
    return update

  def query_inverse_perpetual_position(self, symbol, recv_window=20000):
    method = 'GET'
    path = 'v2/private/position/list'
    params = {'symbol': symbol, 'recv_window': recv_window}
    update = self._query(method, path, params)
    return update

  def query_inverse_delivery_position(self, symbol, recv_window=20000):
    method = 'GET'
    path = 'futures/private/position/list'
    params = {'symbol': symbol, 'recv_window': recv_window}
    update = self._query(method, path, params)
    return update

  def query_usdt_position(self, symbol, recv_window=20000):
    method = 'GET'
    path = 'private/linear/position/list'
    params = {'symbol': symbol, 'recv_window': recv_window}
    update = self._query(method, path, params)
    return update
